Skip to content

13 dataclasses数据类

定义一个类来存储数据,你得写__init____repr____eq__……一堆重复代码。dataclasses模块就是来解决这个问题的——用装饰器自动生成这些方法,代码量直接砍掉一大半。

一、基本用法

1.1 @dataclass装饰器

python
from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
    email: str

# 自动生成了__init__
user = User("大志", 28, "test@example.com")
print(user)  # User(name='大志', age=28, email='test@example.com')

不用手写__init__,不用手写__repr__,装饰器全帮你搞定。

1.2 自动生成的方法

@dataclass默认生成以下方法:

方法默认生成说明
__init__构造函数
__repr__字符串表示
__eq__相等比较
__lt__排序方法,需设置order=True
__hash__哈希值,需设置unsafe_hash=True

二、字段定义

2.1 基本字段

python
from dataclasses import dataclass

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    debug: bool = False

# 使用默认值
config1 = Config()
print(config1)  # Config(host='localhost', port=8080, debug=False)

# 覆盖默认值
config2 = Config(host="0.0.0.0", port=9090, debug=True)

2.2 field()函数

对于可变默认值(如list、dict),需要用field()default_factory参数。

python
from dataclasses import dataclass, field

@dataclass
class Team:
    name: str
    members: list = field(default_factory=list)
    tags: dict = field(default_factory=dict)

team = Team("AI团队")
team.members.append("大志")
print(team)  # Team(name='AI团队', members=['大志'], tags={})

2.3 field()的常用参数

python
from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    age: int
    password: str = field(repr=False)  # 不在__repr__中显示
    internal_id: int = field(init=False, default=0)  # 不参与__init__
    metadata: dict = field(default_factory=dict, repr=False)
参数说明
default默认值
default_factory可变默认值的工厂函数
init是否参与__init__(默认True)
repr是否参与__repr__(默认True)
compare是否参与比较(默认True)
hash是否参与哈希(默认None)

三、装饰器参数

3.1 常用参数

python
from dataclasses import dataclass

# 不可变数据类
@dataclass(frozen=True)
class Point:
    x: int
    y: int

p = Point(1, 2)
p.x = 10  # AttributeError: cannot assign to field 'x'

# 生成排序方法
@dataclass(order=True)
class Student:
    name: str
    score: int

s1 = Student("大志", 85)
s2 = Student("小明", 92)
print(s1 < s2)  # True(按score比较)

# 使用__slots__(3.10+)
@dataclass(slots=True)
class Optimized:
    x: int
    y: int

3.2 装饰器参数一览

参数默认值说明
initTrue生成__init__
reprTrue生成__repr__
eqTrue生成__eq__
orderFalse生成排序方法
unsafe_hashFalse生成__hash__
frozenFalse不可变(设置后不能修改字段)
slotsFalse使用__slots__(3.10+)
kw_onlyFalse所有字段仅限关键字参数(3.10+)

四、后处理

4.1 post_init

__init__之后执行,适合做验证或计算派生字段。

python
from dataclasses import dataclass, field

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)

    def __post_init__(self):
        self.area = self.width * self.height

rect = Rectangle(3, 4)
print(rect.area)  # 12.0

4.2 InitVar

仅在初始化时使用的变量,不存储在实例中。

python
from dataclasses import dataclass, InitVar

@dataclass
class User:
    name: str
    raw_password: InitVar[str]  # 仅初始化时使用
    password_hash: str = field(init=False)

    def __post_init__(self, raw_password):
        self.password_hash = hash(raw_password)

user = User("大志", "123456")
print(user.password_hash)  # 某个哈希值
# user.raw_password 不存在

五、转换操作

5.1 asdict() / astuple()

python
from dataclasses import dataclass, asdict, astuple

@dataclass
class User:
    name: str
    age: int

user = User("大志", 28)

# 转为字典
print(asdict(user))
# {'name': '大志', 'age': 28}

# 转为元组
print(astuple(user))
# ('大志', 28)

5.2 replace()

创建替换字段值的新实例。

python
from dataclasses import dataclass, replace

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    debug: bool = False

config = Config()
new_config = replace(config, port=9090, debug=True)
print(new_config)  # Config(host='localhost', port=9090, debug=True)

5.3 fields()

获取所有字段信息。

python
from dataclasses import dataclass, fields

@dataclass
class User:
    name: str
    age: int
    email: str

for f in fields(User):
    print(f"{f.name}: {f.type}")
# name: <class 'str'>
# age: <class 'int'>
# email: <class 'str'>

六、继承

python
from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    age: int

@dataclass
class Dog(Animal):
    breed: str

dog = Dog("旺财", 3, "柴犬")
print(dog)  # Dog(name='旺财', age=3, breed='柴犬')

有默认值的字段必须在没有默认值的字段之后:

python
from dataclasses import dataclass

@dataclass
class Base:
    x: int
    y: int = 0

@dataclass
class Derived(Base):
    z: int = 0  # 正确:所有字段都有默认值
    # w: int  # 错误:无默认值的字段不能在有默认值的字段之后

七、实用场景

7.1 配置管理

python
from dataclasses import dataclass, field

@dataclass
class DatabaseConfig:
    host: str = "localhost"
    port: int = 5432
    name: str = "mydb"
    user: str = "admin"
    password: str = ""

@dataclass
class AppConfig:
    debug: bool = False
    log_level: str = "INFO"
    database: DatabaseConfig = field(default_factory=DatabaseConfig)

config = AppConfig(debug=True, database=DatabaseConfig(host="192.168.1.100"))

7.2 API响应

python
from dataclasses import dataclass, field, asdict

@dataclass
class ApiResponse:
    code: int = 200
    message: str = "success"
    data: dict = field(default_factory=dict)

    def to_json(self):
        return asdict(self)

response = ApiResponse(data={"name": "大志"})
print(response.to_json())
# {'code': 200, 'message': 'success', 'data': {'name': '大志'}}

7.3 不可变数据

python
from dataclasses import dataclass

@dataclass(frozen=True)
class Vector:
    x: float
    y: float

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2
print(v3)  # Vector(x=4, y=6)

八、与普通类对比

同样的类,两种写法:

python
# 普通类
class User:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age!r})"

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return self.name == other.name and self.age == other.age

# dataclass
@dataclass
class User:
    name: str
    age: int

代码量直接少了三分之二。

九、总结

dataclasses的核心:

功能说明
@dataclass核心装饰器,自动生成方法
field()定义字段的详细属性
asdict() / astuple()转换为字典/元组
replace()创建替换字段值的新实例
fields()获取所有字段信息
__post_init__初始化后处理

dataclasses是Python 3.7引入的,现在已经成为定义数据类的标准方式。如果你还在手写__init__,是时候换@dataclass了。